package panda.server.util.sealedproduct;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;

import panda.client.engine.MagicCard;
import panda.client.enumerations.CardRarity;
import panda.client.enumerations.MagicSet;
import panda.server.data.CardData;
import panda.server.data.MagicCardDB;

/**
 * This class generates boosters and tournament packs in text format. TODO think about making a web interface to this.
 * 
 * @author zane.tian@gmail.com
 */
public class TextProductGenerator {
	// private final static long appStartTime = System.currentTimeMillis();
	private final static boolean includesRules = false;

	private enum SealedType {
		/* 3 boosters, 3 boosters */
		NEW,

		/* 1 tournament pack, 3 boosters */
		OLD
	}

	public static void main(String[] args) throws Exception {
		// System.out.println("Loading at " + (System.currentTimeMillis() - appStartTime) + "ms");

		generateSortedSealedDeck(MagicSet.SCARS_OF_MIRRODIN, MagicSet.SCARS_OF_MIRRODIN);

//		List<CardData> booster = LimitedFactory.get().getPostShardsBoosterPack(MagicSet.RISE_OF_THE_ELDRAZI);
//		displayPackContents(booster);
		
		// openBox(MagicSet.RISE_OF_THE_ELDRAZI, 3, true);
		// outputUniqueCardsInSet();

		// System.out.println("Done in: " + (System.currentTimeMillis() - appStartTime) + "ms");
	}

	/* outputs all unique cards in a set */
	public static void outputUniqueCardsInSet() {
		boolean outputCardList = false;
		if(outputCardList) {
			List<CardData> set = new ArrayList<CardData>();
			set = MagicCardDB.getCardsFromSet(MagicSet.WORLDWAKE);
			for(CardData card : set) {
				System.out.println("1 " + card.getName());
			}
		}
	}

	/**
	 * Simulates the opening of n boxes of any Magic Set and prints the Mythics and Rares opened.
	 */
	public static void openBox(final MagicSet magicSet, final int n, boolean printFoils) {
		int numBoxes = n;

		List<CardData> cards = new ArrayList<CardData>();
		for(int i = 0; i < numBoxes * 36; i++) {
			cards.addAll(LimitedFactory.get().getPostShardsBoosterPack(magicSet));
		}

		List<CardData> openedCards = Collections.unmodifiableList(cards);

		/* foils list */
		if(printFoils) {
			for(CardData card : openedCards) {
				if(card.isFoil()
						&& (card.getRarity().equals(CardRarity.RARE) || card.getRarity().equals(CardRarity.MYTHIC))) {
					System.out.println(card);
				}
			}
		}

		final HashMap<CardData, Integer> cardMap = new HashMap<CardData, Integer>();
		final List<CardData> allCardsInSet = Collections.unmodifiableList(MagicCardDB.getCardsFromSet(magicSet));
		final List<CardData> rares = new ArrayList<CardData>();
		final List<CardData> mythics = new ArrayList<CardData>();

		/* populate the mythic and rare counting hashmap while creating a rares and mythics list of CardData */
		for(CardData card : allCardsInSet) {
			if(card.getRarity().equals(CardRarity.RARE)) {
				rares.add(card);
				cardMap.put(card, 0);
			}

			if(card.getRarity().equals(CardRarity.MYTHIC)) {
				mythics.add(card);
				cardMap.put(card, 0);
			}
		}

		Collections.sort(rares);
		Collections.sort(mythics);

		/* count rares and mythics */
		int totalMythics = 0;
		int totalRares = 0;
		for(CardData card : openedCards) {
			CardRarity rarity = card.getRarity();

			if(rarity.equals(CardRarity.MYTHIC) || rarity.equals(CardRarity.RARE)) {
				cardMap.put(card, cardMap.get(card) + 1);

				if(rarity.equals(CardRarity.MYTHIC)) {
					totalMythics++;
				} else if(rarity.equals(CardRarity.RARE)) {
					totalRares++;
				} else
					assert false : "Assertion Error on " + card + ": must be either mythic or rare";
			}
		}

		/* display all mythics and rares and their card counts */
		System.out.println("\n[Mythics] " + totalMythics);
		for(CardData card : mythics) {
			System.out.println(card + ": " + cardMap.get(card));
		}

		System.out.println("\n[Rares] " + totalRares);
		for(CardData card : rares) {
			System.out.println(card + ": " + cardMap.get(card));
		}
	}

	/**
	 * Generates a old style Sealed Deck of: 1 tournament pack and 3 boosters or a new style Seatled deck of 3 boosters
	 * and 3 boosters.
	 * 
	 * @param mainSet The main set.
	 * @param expansionSet The set of the booster packs.
	 */
	public static void generateSealedDeck(MagicSet mainSet, MagicSet expansionSet, SealedType type) {
		// if(type == SealedType.OLD) { // System.out.println("Tournament Pack " + mainSet.getAbbrev() + " Contents:");
		// for(int i = 0; i < 3; i++) {
		// /* main set */
		// // System.out.println("Booster Pack #" + (i + 1) + " " + mainSet.getAbbrev() + " Contents:");
		// List<CardData> booster = LimitedFactory.get().getPreShardsBoosterPack(mainSet);
		// displayPackContents(booster);
		// }
		//
		// /* expansion set */
		// for(int i = 0; i < 3; i++) {
		// // System.out.println("Booster Pack #" + (i + 1) + " " + expansionSet.getAbbrev() + " Contents:");
		// List<CardData> booster = LimitedFactory.get().getPreShardsBoosterPack(expansionSet);
		// displayPackContents(booster);
		// }
		// } else

		if(type == SealedType.NEW) {
			for(int i = 0; i < 3; i++) {
				/* main set */
				// System.out.println("Booster Pack #" + (i + 1) + " " + mainSet.getAbbrev() + " Contents:");
				List<CardData> booster = LimitedFactory.get().getPostShardsBoosterPack(mainSet);
				displayPackContents(booster);
			}

			/* expansion set */
			for(int i = 0; i < 3; i++) {
				// System.out.println("Booster Pack #" + (i + 1) + " " + expansionSet.getAbbrev() + " Contents:");
				List<CardData> booster = LimitedFactory.get().getPostShardsBoosterPack(expansionSet);
				displayPackContents(booster);
			}
		}
	}
	
	/**
	 * Generates a list of cards sorted by color, then rarity. Hardcoded for Rise of the Eldrazi because 
	 * the official oracle text is not out yet and 3 days before the pre-release, all I had to work with
	 * was the mtgsalvation spoiler.
	 * @param mainSet
	 * @param expansionSet
	 */
	public static void generateSortedSealedDeck(MagicSet mainSet, MagicSet expansionSet) {
		List<CardData> pool = new ArrayList<CardData>();
		
		for(int i = 0; i < 3; i++) {
			/* main set */
			// System.out.println("Booster Pack #" + (i + 1) + " " + mainSet.getAbbrev() + " Contents:");
			List<CardData> booster = LimitedFactory.get().getPostShardsBoosterPack(mainSet);
			pool.addAll(booster);
			// displayPackContents(booster);
		}

		/* expansion set */
		for(int i = 0; i < 3; i++) {
			// System.out.println("Booster Pack #" + (i + 1) + " " + expansionSet.getAbbrev() + " Contents:");
			List<CardData> booster = LimitedFactory.get().getPostShardsBoosterPack(expansionSet);
			pool.addAll(booster);
			// displayPackContents(booster);
		}
		
		/**
		 * Compares by rarity first, then by converted casting cost, then by card name
		 */
		Comparator<CardData> rarityComparator = new Comparator<CardData>() {
			public int compare(CardData o1, CardData o2) {
				if(o1.getRarity().getLevel() > o2.getRarity().getLevel()) {
					return 1;
				} else if(o1.getRarity().getLevel() < o2.getRarity().getLevel()) {
					return -1;
				} else {
					/* both rarities are equal */
					int o1cc = Integer.valueOf(o1.getConvertedCastingCost());
					int o2cc = Integer.valueOf(o2.getConvertedCastingCost());
					if(o1cc > o2cc) {
						return 1;
					} else if(o1cc < o2cc) {
						return -1;
					} else if(o1cc == o2cc) {
						return o1.getName().compareTo(o2.getName());
					} else {
						throw new IllegalStateException();
					}
				}
			}
		};
		
		List<CardData> white = new ArrayList<CardData>();
		List<CardData> blue = new ArrayList<CardData>();
		List<CardData> black = new ArrayList<CardData>();
		List<CardData> red = new ArrayList<CardData>();
		List<CardData> green = new ArrayList<CardData>();
		List<CardData> multicolored = new ArrayList<CardData>();
		List<CardData> artifacts = new ArrayList<CardData>();
		List<CardData> nonbasicLand = new ArrayList<CardData>();
		List<CardData> basicLand = new ArrayList<CardData>();
		
		for(CardData card : pool) {
			int id = card.getID();
			
			if(isBetween(id, 1, 27)) {
				white.add(card);
			} else if(isBetween(id, 28, 53)) {
				blue.add(card);
			} else if(isBetween(id, 54, 80)) {
				black.add(card);
			} else if(isBetween(id, 81, 107)) {
				red.add(card);
			} else if(isBetween(id, 108, 134)) {
				green.add(card);
			} else if(id == 135) {
				multicolored.add(card);
			} else if(isBetween(id, 136, 223)) {
				artifacts.add(card);
			} else if(isBetween(id, 224, 229)) {
				nonbasicLand.add(card);
			} else if(isBetween(id, 230, 249)) {
				basicLand.add(card);
			} else {
				throw new IllegalStateException("ID not valid: " + card.getName() + " " + id + " " + card.getMagicSet().getName());
			}
		}

		Collections.sort(white, rarityComparator);
		Collections.sort(blue, rarityComparator);
		Collections.sort(black, rarityComparator);
		Collections.sort(red, rarityComparator);
		Collections.sort(green, rarityComparator);
		Collections.sort(multicolored, rarityComparator);
		Collections.sort(artifacts, rarityComparator);
		Collections.sort(nonbasicLand, rarityComparator);
		Collections.sort(basicLand, rarityComparator);
		
		System.out.println("\n\n// White: " + white.size());
		for(CardData card : white) {
			System.out.println("1 " + card.getName());
		}
		
		System.out.println("\n\n// Blue: " + blue.size());
		for(CardData card : blue) {
			System.out.println("1 " + card.getName());
		}
		
		System.out.println("\n\n// Black: " + black.size());
		for(CardData card : black) {
			System.out.println("1 " + card.getName());
		}
		
		System.out.println("\n\n// Red: " + red.size());
		for(CardData card : red) {
			System.out.println("1 " + card.getName());
		}
		
		System.out.println("\n\n// Green: " + green.size());
		for(CardData card : green) {
			System.out.println("1 " + card.getName());
		}
		
		System.out.println("\n\n// Multicolored: " + green.size());
		for(CardData card : multicolored) {
			System.out.println("1 " + card.getName());
		}
		
		System.out.println("\n\n// Artifacts: " + artifacts.size());
		for(CardData card : artifacts) {
			System.out.println("1 " + card.getName());
		}
		
		System.out.println("\n\n// Non Basic Land: " + nonbasicLand.size());
		for(CardData card : nonbasicLand) {
			System.out.println("1 " + card.getName());
		}
		
		System.out.println("\n\n// Basic Land: " + basicLand.size());
		for(CardData card : basicLand) {
			System.out.println("1 " + card.getName());
		}
		
//		System.out.println("\n\n\n\nMTGO Output: ");
//		for(CardData card : pool) {
//			System.out.println("1 " + card.getName());
//		}
		
	}
	
	private static boolean isBetween(int subject, int lowerBound, int upperBound) {
		if(subject >= lowerBound && subject <= upperBound) {
			return true;
		} else
			return false;
	}

	/**
	 * Displays a formatted representation of a pack of cards.
	 * 
	 * @param cards The contents of the pack.
	 */
	private static void displayPackContents(List<CardData> cards) {
		for(CardData card : cards) {
			// System.out.print(card.getRarity().getRarityChar() + " ");

			/* casting cost is in format: 6WW (8) */
			// String costs[] = card.getCastingCost().split(" ");

			// System.out.print(pad(costs[0], 12) + " ");
			// System.out.print(pad(card.getName(), 30));
			System.out.print("1 " + card.getName());

			if(includesRules) {
				System.out.print(pad(card.getType(), 45));
				System.out.print(card.getBodyText());
			}

			System.out.println();
		}
		System.out.println("");
	}

	/**
	 * TODO Unfinished method. Outputs HTML to display cards in a webpage.
	 * 
	 * @param cards The list of magiccards to be displayed.
	 */
	@SuppressWarnings("unused")
	private static void generateHTMLImageGallery(List<MagicCard> cards) {
		for(MagicCard card : cards) {
			System.out.println("<img src='" + card.getUrl75() + "'/>");
		}
	}

	/**
	 * Pads a String with spaces up to a set length. Similar to C++'s setw
	 * 
	 * @param input The String to be padded.
	 * @param width The width (in characters) to pad.
	 * @return A String with size "width" where any trailing characters are spaces.
	 */
	public static String pad(String input, int width) {
		int targetLength = input.length();
		if(width <= targetLength) {
			throw new IllegalArgumentException("Padding length " + width + " is not enough to pad target: " + input);
		}

		StringBuffer result = new StringBuffer();
		result.append(input);

		for(int i = 0; i < width - targetLength; i++) {
			result.append(" ");
		}

		return result.toString();
	}
}